perm filename TCPPZ.MAC[IP,SYS] blob sn#680228 filedate 1982-10-14 generic text, type T, neo UTF8
;CWL:<403-TCP>TCPPZ.MAC.40303  6-May-82 17:45:57, Edit by CLYNN
; Make packetizer combine user buffers into single packet
; Don't always fill window; EMTPKT uses time passed in T1
;<403-TCP>TCPPZ.MAC.40301 29-Jan-82 15:07:12, Edit by CLYNN
; Updated for TCP release 3
;[BBND]<401-TCP>TCPPZ.MAC.156, 13-Oct-81 17:45:00, Ed: CLYNN
; Fix: TVT LINBLK=-1 and testing PPROG in possibly vanished packet
;[BBNF]<401-TCP>TCPPZ.MAC.155, 14-Aug-81 14:03:00, Ed: CLYNN
; Fix: Call to LCKTTY with TVTL=0 after PKTIZ1, Call to TVTOSP with
; LINBLK=-1 after PKZ23A
;[BBNF]<401-TCP>TCPPZ.MAC.154, 10-Jul-81 14:13:00, Ed: CLYNN
; Fix: No room in PKTZ9A, use T3 at PKTZ18, use TPKT for TCP checksum
; in ABTNTC, no room in PZINI
;<401-TCP>TCPPZ.MAC.153,  4-Apr-81 17:25:46, Edit by TAPPAN
; merged with multiple net stuff

	SEARCH	INPAR,TCPPAR,PROLOG
	TTITLE	TCPPZ
	SUBTTL	TCP PACKETIZER, WILLIAM W. PLUMMER, 7JAN77
	SWAPCD

COMMENT !

	The PACKETIZER is called with TCB setup to point at a
	(locked) connection block.  It attempts to form packets
	from data in any buffers which are queued from the
	user SENDs.  If the "force packet" bit is on, the
	PACKETIZER will always generate a packet containing
	an ACK, even if there is no data to be sent. Packetizing
	continues until no more is available from user buffers or
	until the send window has been filled.

	In the case of virtual terminals (TVTs) output is
	stored in TTY buffers and TVTNOF is set to cause a
	scan by OPSCAN which forces a packet on TCBs which
	are TVTs.  PZ runs with BFR set to 0 and BFRCNT
	set to infinity in this case since it is not known
	how much output is waiting to go and since the buffers
	are non-standard format.



* PKTIZE ...  3 ...... Construct packets for a connection
  SETISN ... 12 ...... Set initial window and send sequence
  SNDSYN ... 12 ...... Send a SYN
  SNDDAT ... 13 ...... Send data
  SNDFIN ... 15 ...... Send a FIN

* EMTPKT ... 16 ...... Emit a packet into the network
  ABTNTC ... 18 ...... Say this end of connection was abandonned
* SCRCLS ... 19 ...... Send a Secure Close to BCR

* FRCPKT ... 20 ...... Cause packetizer to emit a packet 
* ENCPKT ... 21 ...... Wait a while then force a packet
* DLAYPZ ... 21 ...... Ask background to run packetizer later

* FLSSBF ... 22 ...... Flush SEND buffers
* PZINI .... 23 ...... Initialize the PZ process block

	!

;Packetizer

;TCB/	(Extended) Pointer to connction block
;
;	CALL PKTIZE
;Ret+1:	Always


PKTIZE::LOCAL <BUFCNT,XFRCNT,WNDSPC,LINBLK>
	PUSH P,PKT
	PUSH P,TPKT
	PUSH P,BFR
	SETO LINBLK,		; Assume not TVT (abort case)

; User did an ABORT for this connection or a RESET, CLZFF, LOGOUT etc.
; If a foreign address is known and we're not NOT.NOT a "non-existant"
; TCB error is sent to it so it can know that the connection is gone
; on this end.

	JE TSABT,(TCB),PKTIZ0	; User requested ABORT?
	LOAD T1,TRSYN,(TCB)	; Get state of Recv and
	LOAD T2,TSSYN,(TCB)	; Send synchronization
	CAIN T1,NOTSYN
	 CAIE T2,NOTSYN
	  CAIN T1,SYNABL	; If we know the foreign address,
	   CAIA
	    CALL ABTNTC		; Send a courtesy error pkt to other end

	MOVX T1,ELP+↑D7		; "No such connection"
	CALL ABTCON		; Set to NOTSYN, flush buffers, queues
	SETZRO <TSUOP,TSFP>,(TCB) ; Fake user CLOSE, Clear Force Packet request
	CALL USRABD		; Tell user that ABORT is done.
	JRST PKTIZX
PKTIZ0:

; If packet is being encouraged, set Force packet bit to get it done.

	JE TSEP,(TCB),PKTZ00
	SETONE TSFP,(TCB)
PKTZ00:

; If a SYN is sent when the connection is first used,
; it should have a sequence number gotten from the "Initial
; Sequence Number" curve (a function of the clock).
;PKTIZ1:
	LOAD T1,TSSYN,(TCB)	; Get send state
	CAIN T1,SYNABL		; SYNCABLE?
	  CALL SETISN		; Yes. Set initial sequence number

; If this is a TVT, TSFP will be on but there will be no buffer.
; Do the special things for this case.

	JE TTVT,(TCB),PKTZ1D	; Jump if not a TVT
	MOVX BFRCNT,177700	; Maybe lots of output to handle
	LOAD T2,TVTL,(TCB)	; Get the line number
	JUMPE T2,PKTZ1D		; None if no TVT assigned (not synced)
	CALL LCKTTY		; Lock TTY, trm blk address to T2 & NOINT
	  JUMPLE T2,PKTZ1C	; Can't.
	MOVEM T2,LINBLK		; Save the address
	CALL TTSOBE		; CFOBF might have happened since OPSCAN
	  JRST PKTZ1E		; Chrs available. (Zero if ↑S)
	MOVE T2,LINBLK		; Restore line blk address
PKTZ1C:	CALL ULKTTY		; Decrease the lock count & OKINT
PKTZ1D:	SETO LINBLK,		; No terminal block to unlock later

; Top of main non-TVT loop

;Try to find a user buffer to send data from.  This could the the
; "send current buffer" which is left from a previous call or a
; buffer queued from user SEND.  If there is no buffer, BFR is set
; to 0 as is the byte count.

PKTIZ1:	LOAD BFR,TSCB,(TCB)	; Get current send buffer if any
	JUMPN BFR,PKTIZ3	; Got one. Go set count.
	LOAD T1,QNEXT,<+TCBSBQ(TCB)> ; Get next thing on send buf Q
	CAIN T1,TCBSBQ(TCB)	; Next points at header ...
	  JRST PKTZ1E		; means empty.  No buffer.
	SETSEC T1,INTSEC	; Make extended address
	CALL DQ			; Dequeue the buffer
	SKIPA BFR,T1		; And setup the standard pointer.
PKTZ1E:	  MOVX BFR,0		; 0 means no buffer
	STOR BFR,TSCB,(TCB)	; Remember as current buffer

; Top of main TVT loop

; If there is a current buffer, set BUFCNT from the sum of the buffers.
; If no current buffer, try for a TVT.  If neither, set BUFCNT to 0.

PKTIZ2:	JUMPN BFR,PKTIZ3	; Jump if we have a buffer
	MOVX BUFCNT,0		; Count if not a TVT
	JUMPL LINBLK,PKTIZ4	; Jump if not TVT, or TVT w/ empty output buf
	MOVE T2,LINBLK		; Arg for TVTOSP
	CALL TVTOSP		; Find out how many chrs to be sent
	MOVE BUFCNT,T1		; May be zero
	STOR T1,TSBYT,(TCB)	; Keep TSBYT up to date too
	JRST PKTIZ4

PKTIZ3:	SETSEC BFR,INTSEC	; Make extended address
	LOAD BUFCNT,TSBYT,(TCB)	; Get total (SEND) queued byte count
PKTIZ4:

; Force our idea of the availble window space to 0 if we cannot send
; data but have to generate only an ACK.

	LOAD T1,TSSYN,(TCB)	; Send state
	LOAD T2,TRSYN,(TCB)	; Receive state
	CAIE T1,SYNABL		; If send side is SYNABL or
	 CAIN T2,SYNABL		; Recv side is SYNABL then
	  JRST PKTIZ5		; Make useable window 0

; Compute the amount of window space available to send into as
; provided by the remote end.

	LOAD T1,TSLFT,(TCB)	; Send Left
	LOAD T3,TSWND,(TCB)	; Send Window offered
	SKIPN T3		; Allow sending if window is shut
	  MOVX T3,1		; Need probe to sense remote window openning
	LOAD T2,TSSEQ,(TCB)	; Send Sequence
	ADD T1,T3		; Compute Send Right
	SUB T1,T2		; Minus Sequence
	MODSEQ T1		; Keep within right number of bits
	CAML T1,[MAXSEQ/2]	; If window space is .lt. 0 then
	  JRST PKTIZ5		; Make it zero
	MOVEM T1,WNDSPC		; Amount of useable window space

	LOAD T2,TSMXP,(TCB)	; Max size of a packet (incl. header)
	CAML T1,T2		; If window is as large as a packet
	  JRST PKTIZ6		; Go use it
	LSH T1,2		; 4*Useable
	CAML T1,T3		; If Useable/Offered .ge. 1/4
	  JRST PKTIZ6		; Use it
	JUMPE BFR,PKTIZ5	; Cannot have PUSH if no buffer
	JN BEOL,(BFR),PKTIZ6	; Use anything if PUSH
PKTIZ5:	SETZ WNDSPC,		; Make useable window 0
PKTIZ6:

	SKIPLE BUFCNT		; If no data or
	 JUMPG WNDSPC,PKTIZ7	; No useable window
	  JE TSFP,(TCB),PKTIZX	; Give up unless Force Pkt on.
PKTIZ7:

; Now the number of bytes available from the user buffer(s) is
; known and the apparent amount of useable window space is known.
; Set XFRCNT to the (maximum) amount which can actually be sent in this
; Pkt.  In the case of a TVT, it is not known how much is available
; and we will assume a full packet (or window, etc) is to be sent.

	CAML BUFCNT,WNDSPC	; Take min of what is available to be
	 SKIPA XFRCNT,WNDSPC	; sent and space allowed to send in
	  MOVE XFRCNT,BUFCNT
	CAMLE XFRCNT,INTXPB	; Limit (roughly) to what a
	 MOVE XFRCNT,INTXPB	; Pkt can hold.

REPEAT 0,<
; Check to see if sequence numbers are being consumed at so high
; of a rate that the current packet may cross into the forbidden zone.

	CALL GETISN		; Get initial sequence number
	ADD T1,[SNSTEP]		; Plus a clock tick's worth of Seq nums.
	MODSEQ T1
	MOVEM T1,T2		; Lower bound of forbidden region
	LOAD T1,TSSEQ,(TCB)	; Current Send Sequence (to be Pkt Seq)
	MOVE T3,T1
	ADDI T3,5(XFRCNT)	; Allow for possible controls in Pkt
	MODSEQ T3
	CALL CHKWND		; Will Pkt cross into forbidden zone?
	JUMPE T1,PKTIZ9		; Jump if not.

	JN TSFP,(TCB),PKTIZ8	; Jump if we HAVE to emit a packet
	MOVX T1,↑D1000		; Wait for a second for more
	CALL DLAYPZ		; sequence numbers to become available.
	INCR TCTSQ,(TCB)	; Count them
	JRST PKTIZX		; Background will cause PZ to run then.
PKTIZ8:	MOVX XFRCNT,0		; Don't send any data in Pkt.
PKTIZ9:
> ; End of REPEAT 0

; See if have a current packet to continue filling

	LOAD PKT,TSCPK,(TCB)	; Partially filled packet?
	JUMPE PKT,PKTZ10	; No
	XMOVEI TPKT,PKTELI(PKT)	; Locate TCP header
	LOAD T1,PIDO,(PKT)
	ADD TPKT,T1
	LOAD T1,PTCKS,(TPKT)	; Total max packet size
	LOAD T2,PIPL,(PKT)	; Current length
	SUB T1,T2		; Unused data space in pkt (NB in T1)
	JRST PKTZ12
PKTZ10:

; Try to assign a block of free storage for the packet to be sent.
;	MOVEI T1,(XFRCNT)	; Number of data bytes
	LOAD T1,TSMXP,(TCB)	; Get a big packet
	SETZ T2,		; Have a TCB
	CALL TCPIPK		; Get packet & fill in headers
	  JRST [MOVX T1,↑D2000	; Two seconds later,
		CALL ENCPKT	; Try again.
		INCR TCTBS,(TCB) ; Count them
		JRST PKTIZX]
	LOAD T2,PIPL,(PKT)	; Length of headers
	ADD T2,T1		; Max IP+TCP+Data length
	STOR T2,PTCKS,(TPKT)	; Save it in case continue filling later
	PUSH P,T1		; T1 is max packet data count

; Enter the send sequence for the connection as the sequence number
; of the packet.

	LOAD T1,TSSEQ,(TCB)	; Current send sequence
	STOR T1,PSEQ,(TPKT)	; Packet sequence number

; Send a SYN if connection is opening

	LOAD T1,TSSYN,(TCB)	; Get send state
	CAIN T1,SYNABL		; SYNCHABLE means we must tell other
	  CALL SNDSYN		; end our seq. num. by sending a SYN
				; PSYN to 1 & TSSYN to SYNSNT
	POP P,T1		; Max # data octets

; Transfer data from the user buffer (if any) to the packet.  XFRCNT
; has the maximum number of bytes, which may be 0.  T1 has unused bytes
; in packet.  Find how much can really be sent, given options & header

PKTZ12:	CAMLE XFRCNT,T1		; Min against available data
	  MOVEI XFRCNT,(T1)	; Cannot send all the data in pkt
	MOVE T1,XFRCNT		; # of octets for SNDxxx

; Call appropriate data transfer routine

	JUMPE BFR,PKTZ14	; Jump if no buffer from user SEND (or a TVT)
	CALL SNDDAT		; Transfer it from user buffer to Pkt
	JRST PKTZ15		; Note that SNDDAT set timestamp
PKTZ14:
	SKIPGE T2,LINBLK	; Do we have a terminal block?
	  JRST PKZ14A		; No.
	CALL SNDTVT		; Send data from a virtual terminal
	SETONE PEOL,(TPKT)	; Hussle up receiver
PKZ14A:
	MOVE T2,TODCLK		; Current millisecond
	STOR T2,PTS,(PKT)	; Set the Packet timestamp
PKTZ15:	MOVEM T1,XFRCNT		; Save number actually sent

	LOAD T1,PIPL,(PKT)	; Get packet length (b) witout data
	ADD T1,XFRCNT		; Add amount just inserted
	STOR T1,PIPL,(PKT)	; Set into Internet Packet Length

	LOAD T1,TSBYT,(TCB)	; Reduce queued count too
	SUB T1,XFRCNT
	STOR T1,TSBYT,(TCB)

; Send a FIN if it is time.  User must have said CLOSE (TSUOP
; bit is off), SEND connection must be synchronized, and there must be
; nothing waiting to be sent (no current send buffer and nothing Q'd)

	LOAD T1,TSSYN,(TCB)	; Get send state
	CAIE T1,SYNCED		; Connection synchronized?
	  JRST PKTZ16		; No.  No FIN can be sent.
	JN TSUOP,(TCB),PKTZ16	; Jump if connection still OPEN by user
	JN TSCB,(TCB),PKTZ16	; Still something to send. No FIN yet.
	LOAD T1,QNEXT,<+TCBSBQ(TCB)>	; Get first thing on send bfr q
	CAIN T1,TCBSBQ(TCB)	; Is the queue empty?
	  CALL SNDFIN		; Include a FIN in this packet
PKTZ16:				; PFIN is 1, TSSYN=FINSNT, DG scheduled
						; if R=NOTSYN

; If we are ACKing a remote FIN, the receive side becomes NOTSYNCHED.
; If that makes both sides NOTSYNCHED, the user is notified that the
; connection is fully closed.  If just the receive side closed, the
; user is told that the connection is closing.  If all that remains
; to happen on the connection is that this end should receive an ACK
; of our FIN, Background is notified to generate a fake ACK after a
; reasonable time; this guards against the network losing the final ACK.

	LOAD T1,TRSYN,(TCB)	; Get receive state
	CAIE T1,FINRCV		; FINRECEIVED?
	  JRST PKTZ19		; No.  Skip the checks.

	MOVX T1,NOTSYN
	STOR T1,TRSYN,(TCB)	; Change to NOTSYNCHED
	MOVX T1,XLP+↑D12	; "Closing" code (Why not XFP+↑D12??)
	CALL FLSRBF		; Flush receive buffers with this code

	LOAD T2,TSSYN,(TCB)	; Get send state
	CAIE T3,FINSNT		; Sending a FIN now?
	  JRST PKTZ17		; No
	XMOVEI T1,DG		; Who to signal
	MOVE T2,TCPDGT		; When to signal
	CALL SIGNAL
	JRST PKTZ20		; Force this packet out
PKTZ17:
	MOVX T1,XFP+↑D12	; Assume "Closing"
	CAIN T2,NOTSYN		; Send side already closed?
	  MOVX T1,XLP+↑D3	; Yes, "Closed" event
	CALL USREVT		; Tell user
PKTZ19:
; Decide whether to wait for more data or send what packet now holds

	JN TSFP,(TCB),PKTZ20	; Send it if Force Packet is on
	JN <PSYN,PEOL,PFIN>,(TPKT),PKTZ20 ; or packet contains control
	LOAD T1,PIPL,(PKT)	; or if packet is full
	LOAD T2,PTCKS,(TPKT)
	CAMN T1,T2
	  JRST PKTZ20		; Full, send it
	STOR PKT,TSCPK,(TCB)	; Packet to be held
	SETZRO <TSFP,TSEP>,(TCB) ; Clear signals
	JRST PKTIZX

; Packet will be sent now, finish it off

PKTZ20:	SETZRO TSCPK,(TCB)	; No saved packet
	SETZRO <TSFP,TSEP>,(TCB) ; Clear signals

; Now all control and data have been stored in the packet. Advance
; the send sequence in the TCB to include all of this packet.

	CALL PKTEND		; Returns next seq num after this Pkt
	STOR T1,TSSEQ,(TCB)	; Advance Send sequence
	STOR T1,PESEQ,(PKT)	; Save recomputed end of packet

	JE PSYN,(TPKT),PKZ201	; Jump if not first packet on this conn
	LOAD T2,TSLFT,(TCB)	; Get send left
	SUB T1,T2		; Compute amount of window taken by this
	MODSEQ T1		; first Pkt.
	STOR T1,TSWND,(TCB)	; And prevent further sends until window
PKZ201:				; info arrives from other end.

	CALL NULPKT		; See if anything retransmittable here
	SETCA T1,		; Get sense right
	STOR T1,PPROG,(PKT)	; Say program must retain the packet

	CALL SETRXP		; Setup packet rexmit parameters

; Set the timegenerated word in the local header.  Used to compute
; roundtrip time for determining what the retransmit interval will be.

	MOVE T1,TODCLK		; Current millisecond
	STOR T1,PTG,(PKT)	; Packet Time Generated

; Done filling the packet.  If running in  secure mode and this
; packet has something which will be acknowledged, make the
; current level be the next level so as to shut off subsequent
; connection change request options to the KDC.

	SKIPN INTSCR		; In secure mode?
	  JRST PKTZ21		; No.  Avoid the overhead.
	JE PPROG,(PKT),PKTZ21	; See if pkt will be ACK'd
	LOAD T2,TSLVN,(TCB)	; Guaranteed that KDC will here the word
	STOR T3,TSLVC,(TCB)	; So update the current level
PKTZ21:

	LOAD T1,PPROG,(PKT)	; Save flag for later test
	PUSH P,T1		; PKT may vanish if error in EMTPKT (SNDGAT)

	AOS PZPKCT		; Count Packetizer packets
	MOVX T1,PT%XX1		; "Being output"
	TDNE T1,INTTRC		; Want trace?
	  CALL PRNPKT		; Yes
	MOVX T1,PT%TPZ
	TDNE T1,INTTRC		; Want trace?
	  CALL PRNPKT		; Yes

; Do statistics functions
	SKIPN STATF		; Taking statistics right now?
	  JRST PKTZ22		; No
	MOVEI T1,OPDLAY		; Histogram time to (null) Output Proc.
	CALL TSTAMP		; Process the time stamp
	MOVEI T1,OPUSE		; Charge time to Output Processor
	XMOVEI T2,EMTPKT	; for call to EmitPacket
	MOVE T3,LINBLK		; TVT block if any
	CALL TIMCAL		; Do a timed call.
	JRST PKTZ23		; Skip non-statistics code

PKTZ22:	MOVE T1,LINBLK		; TVT block if any
	CALL EMTPKT		; EmitPacket
PKTZ23:
	POP P,T1		; Saved PPROG tells if PKT needed RX
	JUMPE T1,PKZ23A		; Not needed (maybe its been sent & freed)
	SKIPN T1,PKT		; What to Enqueue (if there wasn't an error)
	  JRST PKZ23A		; Error in EMTPKT/SNDGAT
				; (ought to do something, ICMP ??)
	XMOVEI T2,TCBRXQ(TCB)	; Pointer to the retransmit queue
	CALL NQ			; Enqueue it there
	XMOVEI T1,RX		; Select the Retransmitter
	LOAD T2,PRXI,(PKT)	; Retransmission interval
	MOVE T4,T2
	ADD T4,TODCLK		; Time of next run
	SKIPE TCBQRX(TCB)	; Not queued, or ...
	 CAMG T4,TCBTRX(TCB)	; Need it sooner than scheduled?
	  CALL SIGNAL		; Cause RX to run after that time
PKZ23A:

; See if Packetizer should run again for this connection.  This is true
; if there is something waiting to be sent and there is window space
; in which to send it.

; TVT checks

	JE TTVT,(TCB),PKTZ24	; Jump if not a virtual terminal
	SETZ T1,		; Assume LINBLK is -1
	SKIPLE T2,LINBLK	; Pointer to dynamic area
	  CALL TVTOSP		; Get amount of output waiting
	STOR T1,TSBYT,(TCB)
	JUMPE T1,PKTIZX		; None right now.
	CAMLE WNDSPC,XFRCNT	; If there is still unused window space
	  JRST PKTIZ2		; Go try for it

	MOVE T1,TVTWTM		; After this number of milliseconds
	CALL DLAYPZ		; Try again
	JRST PKTIZX
PKTZ24:

; Non-TVT checks

	JN TSCB,(TCB),PKTZ25	; Jump if there is a current buffer
	LOAD T1,QNEXT,<+TCBSBQ(TCB)>	; Send buffer queue
	CAIN T1,TCBSBQ(TCB)	; Empty?
	  JRST PKTIZX		; Yes.  Nothing to send.  Return.
PKTZ25:
	CAMLE WNDSPC,XFRCNT	; Is there some unused window space?
	  JRST PKTIZ1		; Yes. Use it.


; Return from the Packetizer

PKTIZX:	SKIPL T2,LINBLK		; Have a TVT line locked?
	  CALL ULKTTY		; Yes.  Unlock it.
	POP P,BFR
	POP P,TPKT
	POP P,PKT
	RESTORE			; POP all locals
	RET

; Set Initial Sequence Number for a connection.
; Sets default initial send window.

;TCB/	(Extended) Locked connection block
;
;	CALL SETISN
;Ret+1:	always

SETISN:	JN TSSV,(TCB),SETIS1	; Jump if the current sequence is valid
	CALL GETISN		; Get current value of ISN curve
	STOR T1,TSSEQ,(TCB)	; Store as current send sequence
	SETONE TSSV,(TCB)	; Indicate sequence is now valid
SETIS1:

	LOAD T1,TSSEQ,(TCB)
	STOR T1,TSLFT,(TCB)	; Move Send Left up to Sequence
	RET			; Wait for window

	MOVE T1,INTXPB		; Maximum data in one packet
	SUBI T1,MINIHS+MINTHS	; without options for biggest net
	STOR T1,TSWND,(TCB)	; is the default initial window
	RET			; (May be reduced when SYN sent)



;Include a SYN bit in the packet

;TCB/	(Extended) Current locked connection block
;PKT/	(Extended) Packet
;TPKT/	(Extended) pointer to TCP part of packet
;
;	CALL SNDSYN
;Ret+1:	always

SNDSYN:	SETONE PSYN,(TPKT)	; Set the SYN bit in the packet
	LOAD T1,TSSYN,(TCB)	; Get send state
	CAIE T1,SYNABLE		; SYNCABLE (ie, opening)
	  JRST SNDSY1		; No.
	MOVX T2,SYNSNT
	STOR T2,TSSYN,(TCB)	; Yes. Change to SYNSENT state.
SNDSY1:	AOS SYNSCT		; Count SYNs sent
	RET

; Move data from user buffer(s) to a (partially filled) packet.
; All OPTIONS must be in the Packet at the time this is called.

;TCB/	(Extended) Locked connection block
;PKT/	(Extended) Packet
;TPKT/	(Extended) pointer to TCP part of packet
;BFR/	(Extended) Buffer header address (of first buffer)
;T1/	Number of bytes to move (maybe 0).
;
;	CALL SNDDAT
;Ret+1:	Always. T1 has number actually transferred

SNDDAT:	LOCAL <CPYCNT,XFRCNT,PKTPTR>
	MOVEM T1,XFRCNT		; Set up the transfer count
	SETZ CPYCNT,

	LOAD T1,PIPL,(PKT)	; Packet length
	IDIVI T1,4		; Divided into
	MOVEI PKTPTR,PKTELI(T1)	; Word offset and byte
	HLL PKTPTR,[	POINT 8,.-.(PKT)
			POINT 8,.-.(PKT),7
			POINT 8,.-.(PKT),15
			POINT 8,.-.(PKT),23](T2)

	SKIPN STATF		; Taking statistics now?
	  JRST SNDDA0		; No
	LOAD T1,BTS,(BFR)	; Get the buffer time stamp
	STOR T1,PTS,(PKT)	; and make that the Packet time stamp
SNDDA0:

; Top of per-user buffer loop

SNDDA1:	CALL SETTUM		; Set user map

	LOAD T4,BCNT,(BFR)	; Available bytes in user buffer
	MOVE T3,T4
	CAILE T3,(XFRCNT)	; Min'ed with remaining space in packet
	  MOVE T3,XFRCNT
	SUB XFRCNT,T3		; Bytes to transfer from next buffer
	SUB T4,T3		; Bytes remaining in user buffer
	STOR T4,BCNT,(BFR)
	ADDM T3,CPYCNT		; Total bytes transferred so far

	LOAD T1,BPTR,(BFR)	; Source is buffer ptr (mapped into mon)
	MOVE T2,PKTPTR		; Destination is packet pointer
	SETO T4,		; Indicate User-to-monitor
	CALL XFRDAT		; Do the data transfer
	MOVEM T2,PKTPTR		; Store updated infomation
	STOR T1,BPTR,(BFR)

	CALL USTTUM		; Unmap user space

; Stop if more remains in user buffer (should have reached end of count

	JN BCNT,(BFR),SNDDAX	; More remains in buffer, wait for next pkt

; Finished with this buffer, transfer PUSH if present

	SETZRO TSCB,(TCB)	; Done with this buffer
	JE BEOL,(BFR),SNDDA5	; Jump if no PUSH in the buffer
	SETONE PEOL,(TPKT)	; Set packet PUSH
SNDDA5:

REPEAT 0,<
	MOVEI T1,PZDLAY		; Select the packetizer delay histogram
	SKIPE STATF		; Taking statistics now?
	  CALL TSTAMP		; Yes. Process the timestamp
>
	MOVX T1,<<OK>B7>	; The general success event code
	CALL USRBFE		; Tell user his send buffer is empty

	MOVX T1,PT%TBD
	TDNE T1,INTTRC		; Want trace?
	  CALL PRNPKT		; Yes

; Stop if transferred full count

	JUMPLE XFRCNT,SNDDAX	; Pkt filled so quit

; Setup for next user buffer

	LOAD T1,QNEXT,<+TCBSBQ(TCB)> ; Get next thing on send buf Q
	CAIN T1,TCBSBQ(TCB)	; Next points at header ...
	  JRST SNDDAX		; means empty.  Something is wrong...stop
	SETSEC T1,INTSEC	; Make extended address
	CALL DQ			; Dequeue the buffer
	MOVE BFR,T1		; And setup the standard pointer.
	STOR BFR,TSCB,(TCB)	; Remember as current buffer
	JRST SNDDA1		; Go to work on this buffer


SNDDAX:	ADDM CPYCNT,BYTSCT	; Count Bytes Sent
	MOVE T1,CPYCNT		; Return bytes actually transferred
	RESTORE
	RET

; Send a FIN in this packet

;TCB/	(Extended) Locked connection block
;PKT/	(Extended) Packet
;TPKT/	(Extended) pointer to TCP part of packet
;
;	CALL SNDFIN
;Ret+1:	always

SNDFIN:	SETONE PFIN,(TPKT)	; Set FIN bit in the packet
	MOVX T1,FINSNT		; New send state
	STOR T1,TSSYN,(TCB)	; Set it.

	LOAD T1,TRSYN,(TCB)	; Get receive state
	CAIE T1,NOTSYN		; NOTSYNCHED?
	  JRST SNDFI1		; No.
	XMOVEI T1,DG
	MOVE T2,TCPDGT
	CALL SIGNAL		; Run again in 30 seconds
SNDFI1:
	AOS FINSCT		; Count FINs sent
	RET

; Emit a packet into a network

;TCB/	(Extended) Locked connection block
;PKT/	(Extended) Packet, NB: It may be invalid on return from SNDGAT
;TPKT/	(Extended) pointer to TCP part of packet
;T1/	Pointer to dynamic data if TVT w/ output data, or -1

EMTPKT::LOCAL <LINBLK>
	MOVEM T1,LINBLK		; Save TTYSRV line block if any
	JN PINTL,(PKT),EMTPKX	; Already in use by interrupt level
				; (ReXmit while NET is off or slow)

	LOAD T4,TRSYN,(TCB)	; Get receive state
	CAIN T4,SYNABLE		; SYNCABLE
	  JRST EMTPK1		; Yes.  Cannot ACK anything.

; Insert ACK

	SETONE PACK,(TPKT)	; Set the packet ACK bit
	LOAD T1,TRLFT,(TCB)	; Receive Left is what we want to hear
	STOR T1,PACKS,(TPKT)	; next.  ACK that.
	STOR T1,TRLAK,(TCB)	; Sending an ACK

; Insert receive window

	CAIE T4,FINRCV		; If received FIN or
	 CAIN T4,NOTSYN		; already closed
	  JRST EMTPK0		; Leave window zero
	LOAD T1,TRWND,(TCB)	; Offered window size
	STOR T1,PWNDO,(TPKT)	; into packet
	JE PACK,(TPKT),EMTPK0	; If no ACK, cannot
	LOAD T2,PACKS,(TPKT)	; Receive-left
	ADD T2,T1		; Plus window
	MODSEQ T2		; is Receive-right
	STOR T2,TRLWN,(TCB)	; Last offered
EMTPK0:

; Set urgent if required

	JE TSURG,(TCB),EMTPK1	; Skip following if not in urgent mode
	LOAD T1,TSURP,(TCB)	; End of urgent data
	LOAD T2,PSEQ,(TPKT)	; Sequence number of this packet
	SUB T1,T2		; Offset to urgent pointer
	CAIG T1,<PURGP/<PURGP&<-PURGP>>>	; Limit to max
	  MOVX T1,<PURGP/<PURGP&<-PURGP>>>
	STOR T1,PURGP,(TPKT)	; Set into packet
	SETONE PURG,(TPKT)	; Set the control bit
EMTPK1:

REPEAT 0,<
	SKIPN STATF		; Taking statistics right now?
	  JRST EMTPK2		; No.
	LOAD T1,PTS,(PKT)	; Get the current timestamp
	CALL SETTSO		; and move into the timestamp option
EMTPK2:
>

; Insert checksum

	SETZRO PTCKS,(TPKT)	; Clear the check sum field
	CALL TCPCKS		; Compute the packet checksum
	STOR T1,PTCKS,(TPKT)	; and enter it in the packet

; Log packet

	MOVX T1,PT%XX2		; "passing Output processor" code
	TDNE T1,INTTRC		; Want trace?
	  CALL PRNPKT		; Yes, Tell Pkt printer
	MOVX T1,PT%TRX
	TDNE T1,INTTRC		; Want trace?
	  CALL PRNPKT		; Yes

	MOVE T1,TODCLK		; Current millisecond number
	STOR T1,PXT,(PKT)	; Store as time of most recent transmit

	CALL SNDGAT		; Send it

	AOS OPPKCT		; Count packets output
	AOS OPRNCT		; and Output runs
EMTPKX:	RESTORE
	RET

; Send a RST to remote TCP when this end is abandonned.
; This is a courtesy which serves to speed things up but is not
; required by the protocol.

;TCB/	(Extended) Locked connection block
;
;	CALL ABTNTC
;Ret+1:	always


ABTNTC:	PUSH P,PKT
	PUSH P,TPKT
	SETZB T1,T2		; No data & have TCB
	CALL TCPIPK		; Get packet & fill in headers
	  JRST ABTNTX		; No space.  Not required anyway.

	SETONE PRST,(TPKT)	; Set the reset bit
	LOAD T1,TSSEQ,(TCB)	; Current send sequence
	STOR T1,PSEQ,(TPKT)	; Is the Packet sequence

	LOAD T2,TRLFT,(TCB)	; What we want to hear next (or 0)
	STOR T2,PACKS,(TPKT)	; Is the acknowledge
	SETONE PACK,(TPKT)	; Make the ACK Sequence meaningful
	SETZRO PWNDO,(TPKT)	; 0 window to other end (TCB is dead)

	SETZRO PTCKS,(TPKT)
	CALL TCPCKS		; Compute TCP packet checksum
	STOR T1,PTCKS,(TPKT)	; and insert into packet

	MOVX T1,PT%XX2		; Say Pkt is being sent
	TDNE T1,INTTRC		; Want trace?
	  CALL PRNPKT		; Yes, Print the packet
	MOVX T1,PT%TRX
	TDNE T1,INTTRC		; Want trace?
	  CALL PRNPKT		; Yes

	AOS PZPKCT		; Count Packetized packets
	AOS OPPKCT		; Count Output packets
	AOS RSTSCT		; Count ERRs sent

	CALL SNDGAT		; Sent Pkt to the net. (NB: PPROG is 0)

ABTNTX:	POP P,TPKT
	POP P,PKT
	RET

; Send a "Secure Close"

; After all has been said, one more packet containing a SCLOPT must
; be sent.  This will cause the BCR to contact the KDC in order
; to remove the keys, etc for the connection.

;TCB/	(Extended) Locked connection block
;
;	CALL SCRCLS
;Ret+1:	always

SCRCLS::PUSH P,PKT
	PUSH P,TPKT
	SETZB T1,T2		; No data & have TCB
	CALL TCPIPK		; Get packet & fill in headers
	  JRST SCRCLX		; No.  Don't worry about it however

;?? need help here
	CALL SNDSCL		; Add in the secure close option

	CALL TCPCKS		; Compute TCP packet checksum
	STOR T1,PTCKS,(PKT)	; and insert into packet

	MOVX T1,PT%XX2		; Say Pkt is being sent
	TDNE T1,INTTRC		; Want trace?
	  CALL PRNPKT		; Yes, Print the packet
	MOVX T1,PT%TRX
	TDNE T1,INTTRC		; Want trace?
	  CALL PRNPKT		; Yes

	AOS PZPKCT		; Count Packetized packets
	AOS OPPKCT		; Count Output packets

	CALL SNDGAT		; Sent Pkt to the net. (NB: PPROG is 0)

SCRCLX:	POP P,TPKT
	POP P,PKT
	RET

; Force a packet

; Causes a packet to be emitted even if there is no data to send.
; Done to cause something to be ACKd for instance.

;TCB/	(Extended) Locked connection block
;
;	CALL FRCPKT
;Ret+1:	always

FRCPKT::JN TSFP,(TCB),FRCPKX	; Filter extra calls
	SETONE TSFP,(TCB)	; Set the Force packet request bit
	$SIGNL(PZ,0)		; Make Packetizer run now
FRCPKX:	RET

; Encourage generation of a packet

; A packet is needed to ACK something, but allow time for some data
; to appear so that the ACK can piggyback on it.  Also, more calls
; may be made and we wish to minimize network traffic by not
; generating an ACK-only packet each time.

;TCB/	(Extended) Locked connection block
;T1/	# msec to wait
;
;	CALL ENCPKT
;Ret+1:	always

ENCPKT::JN TSEP,(TCB),ENCPKX	; Already encouraged.  No more needed.
	SETONE TSEP,(TCB)	; Remember a packet is being encouraged
	JFCL ;MOVX T1,↑D100	; The wait time in milliseconds
	CALL DLAYPZ		; Get background to signal PZ(TCB)
ENCPKX:	RET



; Schedule a delayed signal for the packetizer

;TCB/	(Extended) Locked TCB
;T1/	Delay time in milliseconds
;
;	CALL DLAYPZ
;Ret+1:	always

DLAYPZ::JN TSFP,(TCB),DLAYPX	; Already forced.  No need.

	MOVE T2,T1		; Desired delay for SIGNAL
	SKIPN TCBQPZ(TCB)	; Already queued?
	  JRST DLAYPS		; No

	MOVE T1,TCBTPZ(TCB)	; When
	SUB T1,TODCLK
	CAMG T1,T2		; This request sooner?
	  JRST DLAYPX		; No, its later so ignore it

DLAYPS:	XMOVEI T1,PZ		; Select Packetizer
	CALL SIGNAL
DLAYPX:	RET

; Flush all SEND buffers with a given Event Code

;TCB/	(Extended) Locked connection block
;T1/	Event Code: EFP+↑D7; ELP+↑D7; ELP+↑D14, ELT+↑D4 (no TVTs)
;
;	CALL FLSSBF
;Ret+1:	always

FLSSBF::LOCAL <CODE>
	PUSH P,BFR
	MOVEM T1,CODE
	LOAD BFR,TSCB,(TCB)	; Get the current send buffer
	SETZRO TSCB,(TCB)	; Forget the current send buffer
	JUMPE BFR,FLSSB2	; Do we have a buffer here?
	SETSEC BFR,INTSEC	; Make extended address
FLSSB1:	MOVE T1,CODE		; Yes.
	LSH T1,↑D<36-8>		; Position in error byte
	CALL USRBFE		; Tell user it is done

FLSSB2:	LOAD BFR,QNEXT,<+TCBSBQ(TCB)>	; Get next thing on buffer queue
	CAIN BFR,TCBSBQ(TCB)	; Points at head of queue
	  JRST FLSSBX		; means empty queue.  Done.
	SETSEC BFR,INTSEC	; Make extended address
	MOVE T1,BFR
	CALL DQ			; Dequeue the buffer
	JRST FLSSB1

FLSSBX:	POP P,BFR
	RESTORE
	RET

; PZINI			Initialize PZ process block

;	CALL PZINI
;Ret+1:	ALways

PZINI::	LOCAL <PRC>

	MOVEI PRC,PZ		; Pointer to the Process block for PZ
	MOVX T1,QSZ		; Size of a queue head
	CALL GETBLK		; Head must be in same section as items
	JUMPE T1,PZINIX		; No room
	MOVEM T1,PRCQ(PRC)	; Input queue
	CALL INITQ		; Initialize it

	XMOVEI T1,PRCLCK(PRC)	; Lock
	CALL CLRLCK		; Initilize it

	XMOVEI T1,PKTIZE	; Packetizer function
	MOVEM T1,PRCROU(PRC)	; Routine address
	SETOM PRCWAK(PRC)	; No run time yet
	MOVE T1,[<GIW TCBQPZ,TCB>]; Offset of PZ queue in TCB
	MOVEM T1,PRCQOF(PRC)	; Store process block
	MOVE T1,[<GIW TCBTPZ,TCB>]; Offset of PZ run time in TCB
	MOVEM T1,PRCWOF(PRC)	; Store in process block
	HRLOI T1,377777		; Infinity
	MOVEM T1,PRCSGT(PRC)	; Set time of most recent signal
	MOVEI T1,PZRNCT		; Pointer to run counter
	MOVEM T1,PRCRNC(PRC)	; Put in standard place
	MOVEI T1,PZUSE		; Pointer to CPU use meter
	MOVEM T1,PRCTMR(PRC)	; Put in standard place
	HRROI T1,-1		; All ok

PZINIX:	RESTORE
	RET

	TNXEND
	END